package ua.edu.nuos.jeetraining2012.cms.util.google.api.services.youtube;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.Video;
import com.google.api.services.youtube.model.VideoSnippet;
import com.google.api.services.youtube.model.VideoStatus;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import org.slf4j.Logger;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.GoogleAuthorizer;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.predefined.StandardGoogleApiVariables;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.auth.GoogleAuthException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.transport.GoogleTransportException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.youtube.YouTubeUploadException;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.*;

//import com.google.api.services.drive.DriveScopes;

/**
 * Video uploader to YouTube service via HTTP protocol, using Google APIs.
 *
 * @author Victor N. Ganichev victor.ganichev@gmail.com
 * @version 1.0 Date: 09.06.13 Time: 18:29
 * @since 1.0
 */
public class YouTubeUploader {

    /** Define application name */
    final private static String APPLICATION_NAME = StandardGoogleApiVariables.APPLICATION_NAME;

    /** Define file with preset of Google API Client ID & Client secret */
    final private static String AUTH_KEY_FILE = StandardGoogleApiVariables.AUTH_KEY_FILE;

    /** Define upload dir */
    final private static String UPLOAD_DIR = StandardGoogleApiVariables.UPLOAD_DIR;

    /** Define global HTTP_TRANSPORT */
    private static HttpTransport HTTP_TRANSPORT;

    /** Define global JSON_FACTORY */
    private static JsonFactory JSON_FACTORY = new JacksonFactory();

    /** Define file name, that should be upload to YouTube. */
    private static String uploadFileName;

    /** Define video title. You can use here material title as well as your own. */
    private static String videoTitle;

    /** Define video description. You can use material short description here as well as your own. */
    private static String videoDescription;

    /** Define global YouTube service. */
    private static YouTube youTube;

    /** Define accepted video formats. */
    private static String VIDEO_FILE_FORMAT = "video/*";

    /** Define logger. */
    private static Logger logger = org.slf4j.LoggerFactory.getLogger(YouTubeUploader.class);

    // Constructors
    public YouTubeUploader() {}

    /**
     * Upload video file to YouTube.
     * <p/>
     * Use this method to send video file via HTTP protocol with an YouTube API to YouTube service.
     * <p/>
     * Step one: authorization on Google Service by using {@link StandardGoogleApiVariables#AUTH_KEY_FILE}.
     * Step two: save session key chain in {@link StandardGoogleApiVariables#KEY_CHAIN_DIR}.
     * Step three: after all credentials were retrieved, begin uploading video.
     * <p/>
     * Default video permission "anyone who has a link". If all processes finished with success, return a
     * Map with info about just uploaded video. Map contains: ID, Title, Description, Privacy status.
     *
     * @param uploadFileName                File name, that should be uploaded to YouTube.
     * @param videoTitle                    Define video title.
     * @param videoDescription              Define video description.
     * @return                              HashMap with uploaded file info: ID, Title, Description, Privacy.
     * @throws IllegalArgumentException     Thrown if uploadFileName is null or empty.
     * @throws GoogleTransportException     Thrown if can't pre-setup HTTP_TRANSPORT.
     * @throws GoogleAuthException          Thrown if can't authorize on service.
     * @throws YouTubeUploadException       Thrown if can't upload file for some reasons.
     */
    public static Map<String, String> uploadFileToYouTube(String uploadFileName, String videoTitle, String videoDescription)
            throws IllegalArgumentException, GoogleTransportException, GoogleAuthException, YouTubeUploadException {
        logger.debug("uploadFileToYouTube() - started.");

        if (uploadFileName == null || uploadFileName.isEmpty()) {
            logger.error("uploadFileToYouTube() Can't upload file with empty file name, please set up file first.");
            logger.debug("uploadFileToYouTube() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("File name can't be empty.");
        }

        if (videoTitle == null || videoTitle.isEmpty()) {
            logger.warn("uploadFileToYouTube() videoTitle is empty. Use random.");
            Calendar cal = Calendar.getInstance();
            videoTitle = "JavaEE Courses. Material video file." + cal.getTime();
        }

        if (videoDescription == null || videoDescription.isEmpty()) {
            logger.warn("uploadFileToYouTube() videoDescription is empty. Use random.");
            Calendar cal = Calendar.getInstance();
            videoDescription = "JavaEE Courses. Material video file. File uploaded time: " + cal.getTime();
        }

        logger.debug("uploadFileToYouTube() Setting uploadFileName to {}, video title to {} and description to {}",
                uploadFileName, videoTitle, videoDescription);
        setFileInfo(uploadFileName, videoTitle, videoDescription);

        logger.debug("uploadFileToYouTube() Setting up HTTP_TRANSPORT.");
        try {
            HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
            logger.info("uploadFileToYouTube() HTTP_TRANSPORT success set up. {}", HTTP_TRANSPORT);
        } catch (Exception e) {
            logger.error("uploadFileToYouTube() Can't set up HTTP_TRANSPORT. Got an Exception with error: {} " +
                    "and \nTrace:\n {}", e.getMessage(), Arrays.deepToString(e.getStackTrace()));
            logger.debug("uploadFileToYouTube() finished with general GoogleTransportException");
            throw new GoogleTransportException("Can't configure HTTP Transport. Error: " + e.getMessage());
        }

        Credential credential = null;
        logger.debug("uploadFileToYouTube() Start authorization.");
        try {
            List<String> scopes = Lists.newArrayList("https://www.googleapis.com/auth/youtube.upload");

            credential = GoogleAuthorizer.getInstance()
                    //.setServiceScopes(scopes)         // Define just YouTube scope.
                    .authorizeEndUser();
            logger.info("uploadFileToYouTube() User authorized with credentials {}", credential);
        } catch (IOException e) {
            logger.error("uploadFileToYouTube() Can't authorize user at Google. Got error message: {}, \nTrace:\n",
                    e.getMessage(), Arrays.deepToString(e.getStackTrace()));
            logger.debug("uploadFileToYouTube() - finished with GoogleAuthException");
            throw new GoogleAuthException("Can't authorize. Error: " + e.getMessage());
        }

        youTube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).setApplicationName(
                APPLICATION_NAME).build();
        logger.debug("uploadFileToYouTube() Initiated youtube {}", youTube);

        Video video = null;
        String videoId = null;
        String videoStatus = null;
        String videoTitleY = null;
        String videoDesc = null;
        logger.info("uploadFileToYouTube() Trying to upload video to YouTube.");
        try {
            video = uploadFile(false);
            videoId = video.getId();
            videoStatus = video.getStatus().getPrivacyStatus();
            videoTitleY = video.getSnippet().getTitle();
            videoDesc = video.getSnippet().getDescription();
            logger.info("uploadFileToYouTube() Seams that file successful upload to YouTube. File ID: {}, " +
                    "File privacy status: {}", videoId, videoStatus);
        } catch (SocketTimeoutException e) {
            logger.error("uploadFileToYouTube() Can't upload video file to YouTube SocketTimeoutException occurred with Error message: {} and \nTrace:\n {}",
                    e.getMessage(), Arrays.deepToString(e.getStackTrace()));
            logger.debug("uploadFileToYouTube() - finished with YouTubeUploadException.");
            throw new YouTubeUploadException("Can't upload file to YouTube. Error: " + e.getMessage());
        } catch (IOException e) {
            logger.error("uploadFileToYouTube() Can't upload video file to YouTube IOException occurred with Error message: {} and \nTrace:\n {}",
                    e.getMessage(), Arrays.deepToString(e.getStackTrace()));
            logger.debug("uploadFileToYouTube() - finished with YouTubeUploadException.");
            throw new YouTubeUploadException("Can't upload file to YouTube. Error: " + e.getMessage());
        }

        Map<String, String> videoInfo = new HashMap<>();
        videoInfo.put("id", videoId);
        videoInfo.put("privacy", videoStatus);
        videoInfo.put("title", videoTitleY);
        videoInfo.put("description", videoDesc);

        logger.debug("Retrieved video info {}", videoInfo);

        logger.debug("uploadFileToYouTube() - finished.");
        return videoInfo;
    }


    private static void setFileInfo(String fileName, String title, String description) {
        uploadFileName = fileName;
        videoTitle = title;
        videoDescription = description;
    }

    /**
     * Define video privacy.
     * <p/>
     * Available privacy options:
     * <ul>
     *     <li>public &mdash; video available to anyone.</li>
     *     <li>unlisted &mdash; video available to those, who has a link.</li>
     *     <li>private &mdash; video available to owner.</li>
     * </ul>
     *
     * @return          privacy policy.
     */
    private static VideoStatus setPermissions() {
        logger.debug("setPermission() - started.");

        VideoStatus status = new VideoStatus();
        status.setPrivacyStatus("unlisted");

        logger.debug("setPermission() - finished.");
        return status;
    }

    /**
     * Upload video to YouTube.
     * <p/>
     * When finished, in case of success, instance of Video object with uploaded data
     * will be returned.
     *
     * @param isDirectUpload            Set true if you want to use direct upload protocol.
     * @return                          Uploaded video info or null.
     * @throws IOException              If uploading file can't be read.
     */
    private static Video uploadFile(boolean isDirectUpload) throws IOException {
        logger.debug("uploadFile() - started.");

        Video videoMeta = new Video();
        VideoSnippet snippet = new VideoSnippet();
        String absFilePath = UPLOAD_DIR + uploadFileName;
        final java.io.File UPLOAD_FILE = new java.io.File(absFilePath);

        snippet.setTitle(videoTitle);
        snippet.setDescription(videoDescription);
        logger.debug("Creating snippet for video description {}", snippet);

        videoMeta.setSnippet(snippet);
        logger.debug("Adding snippet to video {}", videoMeta);

        InputStreamContent mediaContent = new InputStreamContent(VIDEO_FILE_FORMAT,
                new BufferedInputStream(new FileInputStream(UPLOAD_FILE)));
        mediaContent.setLength(UPLOAD_FILE.length());
        logger.debug("Got upload video file {} with length {} ", UPLOAD_FILE, UPLOAD_FILE.length());

        VideoStatus videoStatus = setPermissions();
        logger.debug("Setting up privacy policy to {}.", videoStatus);

        videoMeta.setStatus(videoStatus);

        /*  Get meta & status back */
        YouTube.Videos.Insert videoInsert = youTube.videos().insert("snippet,status", videoMeta, mediaContent);

        MediaHttpUploader uploader = videoInsert.getMediaHttpUploader();
        uploader.setDirectUploadEnabled(isDirectUpload);
        uploader.setProgressListener(new YouTubeUploadProgress());

        logger.debug("uploadFile() - finished.");
        return videoInsert.execute();
    }
}
